package projeto.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 
 * @author Javatar
 *Classe de recomendação por semelhança de produto escalar
 */

public class RecomendaEscalar implements Recomenda {
	

	static RecomendaEscalar instanciaUnica = null;
        
	
	/**
	 * Método que recomenda estabelecimentos por semelhança entre usuários
	 * @param ests Lista de estabelecimentos cadastrados
	 * @param usuarios Lista de usuarios cadastrados
	 * @param user Usuario a receber recomendação
	 * @param qtdRecomendacoes Quantidade de recomendações desejadas
	 * @return Lista com estabelecimentos recomendados
	 */
	public List<Estabelecimento> getRecomendacao(List<Estabelecimento> ests,List<Usuario> usuarios,Usuario user,int qtdRecomendacoes){
		
		Map<Integer,List<Usuario>> semelhantes = calculaSemelhantes(usuarios, user);
		List<Integer> semelhantesOrdenados = new ArrayList<Integer>(semelhantes.keySet());
		Set<Estabelecimento> estsIndicados = new LinkedHashSet<Estabelecimento>();
		List<Estabelecimento> indicados;
		Collections.sort(semelhantesOrdenados);
		Collections.reverse(semelhantesOrdenados);
		
		for(int i =0; i < semelhantesOrdenados.size(); i++){
			List<Usuario> semelhanteUsers = semelhantes.get(semelhantesOrdenados.get(i));
			for(Usuario u : semelhanteUsers){
				if(estsIndicados.size() >= qtdRecomendacoes )
					break;
				Map<Integer,List<String>> mapIndicados = new HashMap<Integer, List<String>>();
				for(int j = 0; j < u.getOpnioes().size(); j++){
					if(qtdRecomendacoes == 0)
						break;
					int opniaoU = u.getOpnioes().get(j);
					int opniaoUser = user.getOpnioes().get(j);
					if(opniaoUser == 0 && opniaoU > 0){
						if(!(mapIndicados.containsKey(opniaoU))){
							mapIndicados.put(opniaoU, new ArrayList<String>());
						}
						mapIndicados.get(opniaoU).add(LeituraDeDados.opniaoRestaurantes.get(j));
					}
				}
				estsIndicados.addAll(ordenaIndicados(mapIndicados,ests));
			}
		}
		if(qtdRecomendacoes < estsIndicados.size()){
			indicados = new ArrayList<Estabelecimento>(estsIndicados);
			return indicados.subList(0, qtdRecomendacoes);
		}
		indicados = new ArrayList<Estabelecimento>(estsIndicados);
		return indicados;
		
	}
	
	/**
	 * Padrao singleton instancia unica
	 * @return retorna uma instancia unica de RecomendaEscalar
	 */
	public static RecomendaEscalar getInstance(){
		if(instanciaUnica == null)
			instanciaUnica = new RecomendaEscalar();
		return instanciaUnica;
	}
	
	private Map<Integer,List<Usuario>> calculaSemelhantes(List<Usuario> usuarios,Usuario user){
			
			Map<Integer,List<Usuario>> semelhantes = new HashMap<Integer, List<Usuario>>();
			List<Usuario> Users = new ArrayList<Usuario>(usuarios);
			Users.remove(user);
			
			Iterator<Usuario> usersIt = Users.iterator();
			
			while(usersIt.hasNext()){
				Usuario nextUser = usersIt.next();
				Integer conceitoNextUser;
				Integer conceitoUser;
				int semelhanca = 0;
				for(int i =0; i<user.getOpnioes().size();i++){
					conceitoNextUser = nextUser.getOpnioes().get(i);
					conceitoUser = user.getOpnioes().get(i);
					semelhanca += (conceitoNextUser * conceitoUser);
		
				}
				
				if(!(semelhantes.containsKey(semelhanca))){
					List<Usuario> users = new ArrayList<Usuario>();
					semelhantes.put(semelhanca,users);
				}
				semelhantes.get(semelhanca).add(nextUser);
					
			}
			
			return semelhantes;
		}
	
	private List<Estabelecimento> ordenaIndicados(Map<Integer,List<String>> mapIndicados, List<Estabelecimento> ests){
		
		List<Integer> indicadosOrdenados = new ArrayList<Integer>(mapIndicados.keySet());
		List<Estabelecimento> estsIndicados = new ArrayList<Estabelecimento>();
		Collections.sort(indicadosOrdenados);
		Collections.reverse(indicadosOrdenados);
		
		for(int i =0; i < indicadosOrdenados.size(); i++){
			List<String> indicados = mapIndicados.get(indicadosOrdenados.get(i));
			for(int j = 0; j < indicados.size(); j++ ){
				for(Estabelecimento est: ests){
					if(indicados.get(j).equals(est.getNome())){
						estsIndicados.add(est);
					}
				}	
			}
		}
		return estsIndicados;
	}
}
